﻿using Microsoft.Win32;
using SerialPortExp.Properties;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Drawing;
using System.Globalization;
using System.IO;
using System.IO.Ports;
using System.Linq;
using System.Security.Policy;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;


namespace SerialPortExp
{

    //校验类型
    public enum myCheck
    {
        XOR异或校验,
        SUM8累加校验,
        ModBusLRC校验,
        CRC16小端,
        CRC16大端,
        SUM16小端,
        SUM16大端,
        ModBusCRC小端,
        ModBusCRC大端
    }

    public partial class MainFrm : Form
    {

        #region 私有字段

        //串口对象
        SerialPort serialPort = null;

        //timer对象（线程的timer）
        private System.Timers.Timer timerAutoSend = new System.Timers.Timer();

        //标志位--串口打开与否
        private bool isOpen = false;

        //编码格式
        private string myEncoding = "UTF-8";

        //属性--串口状态
        private bool IsOpen
        {
            get { return isOpen; }
            set
            {
                isOpen = value;

                if (value)
                {
                    this.btnOpenPort.Text = "   关闭串口";
                    this.btnOpenPort.Image = Properties.Resources.open;
                }
                else
                {
                    this.btnOpenPort.Text = "   打开串口";
                    this.btnOpenPort.Image = Properties.Resources.close;
                }
            }
        }

        //保存文件的路径
        private string SavePath
        {
            get { return DateTime.Now.ToString("yyyyMMddHHmmss") + ".txt"; }
        }

        //标志位--接收是否暂停
        private bool isReceivePause = false;

        //标志位--是否是帧头
        private bool isHeaderReceive = false;

        //接收区的缓冲区
        private List<byte> receiveBuffer = new List<byte>();

        //发送区的缓冲区
        private List<byte> sendBuffer = new List<byte>();

        //计数接收到的数据
        private int receiveCount = 0;

        //计算清空的数据
        private int cleanCount = 0;

        //计算发送的数据
        private int sendCount = 0;

        //数据解析队列
        private Queue<byte> bufferQueue = null;

        //帧长度
        private int frameLength = 0;


        #endregion


        public MainFrm()
        {
            InitializeComponent();

            InitSettings();

            //实例化数据解析队列
            bufferQueue = new Queue<byte>();


        }

        #region 00_串口相关
        //串口初始化配置
        private void InitSettings()
        {


            //通信串口
            string[] arryPort = SerialPort.GetPortNames();//获取电脑的所有串口(名字)放在数组里
            if (arryPort.Count() > 0)
            {
                this.cmbPort.DataSource = arryPort;//绑定下拉框
                this.cmbPort.SelectedIndex = 0;//初始化选中
            }

            //波特率
            string[] baundRate = new string[] { "2400", "4800", "9600", "19200", "38400" };
            this.cmbBaudRate.DataSource = baundRate;
            this.cmbBaudRate.SelectedIndex = 2;//9600

            //校验位
            string[] checkDigit = new string[] { "NONE", "ODD", "EVEN" };
            this.cmbCheckDigit.DataSource = checkDigit;
            this.cmbCheckDigit.SelectedIndex = 0;//none

            //数据位
            string[] dataBits = new string[] { "5", "6", "7", "8" };
            this.cmbDataBits.DataSource = dataBits;
            this.cmbDataBits.SelectedIndex = 3;//8

            //停止位
            string[] stopBits = new string[] { "1", "1.5", "2" };
            this.cmbStopBits.DataSource = stopBits;
            this.cmbStopBits.SelectedIndex = 0;//1

            //校验类型
            this.cmbCheck.DataSource = Enum.GetNames(typeof(myCheck));
            this.cmbCheck.SelectedIndex = 0;

            //快捷指令
            this.btnQuickCmd01.Text = Settings.Default.QuickButton01;
            this.btnQuickCmd01.Tag = Settings.Default.QuickCommand01;
            this.txtQuickCmd01.Text = btnQuickCmd01.Tag.ToString();

            this.btnQuickCmd02.Text = Settings.Default.QuickButton02;
            this.btnQuickCmd02.Tag = Settings.Default.QuickCommand02;
            this.txtQuickCmd02.Text = btnQuickCmd02.Tag.ToString();

            this.btnQuickCmd03.Text = Settings.Default.QuickButton03;
            this.btnQuickCmd03.Tag = Settings.Default.QuickCommand03;
            this.txtQuickCmd03.Text = btnQuickCmd03.Tag.ToString();

            this.btnQuickCmd04.Text = Settings.Default.QuickButton04;
            this.btnQuickCmd04.Tag = Settings.Default.QuickCommand04;
            this.txtQuickCmd04.Text = btnQuickCmd04.Tag.ToString();

            //命令模式
            //txt
            this.txtFrameHeader.Text = Settings.Default.CmdHead;
            this.txtData.Text = Settings.Default.CmdData;
            this.cmbCheck.SelectedIndex = Settings.Default.CmdCheck;
            this.txtFrameTail.Text = Settings.Default.CmdTail;

            //ckb
            this.ckbFrameHeader.Checked = Settings.Default.CkbHead;
            this.ckbData.Checked = Settings.Default.CkbData;
            this.ckbCheck.Checked = Settings.Default.CkbCheck;
            this.ckbFrameTail.Checked = Settings.Default.CkbTail;

        }


        //打开串口
        private void btnOpenPort_Click(object sender, EventArgs e)
        {

            //判断端口是否打开--未打开则进行初始化信息配置
            if (IsOpen==true)
            {
                try
                {
                    this.serialPort.Close();//添加异步操作，防止卡死
                }
                catch (Exception ex)
                {

                    throw ex;
                }
                finally
                {
                    this.serialPort.Dispose();//释放资源
                    IsOpen = false;
                }
            }
            else
            {
                try
                {
                    //实例化串口对象
                    serialPort = new SerialPort();

                    //串口初始化配置
                    this.serialPort.WriteBufferSize = 4096;//设置默认(2048)字节大小为4096

                    this.serialPort.ReadBufferSize = 4096 * 2;

                    this.serialPort.PortName = this.cmbPort.Text.Trim();

                    this.serialPort.BaudRate = Convert.ToInt32(this.cmbBaudRate.Text.Trim());

                    this.serialPort.DataBits = Convert.ToInt32(this.cmbDataBits.Text.Trim());//数据位

                    switch (this.cmbCheckDigit.SelectedIndex)//校验位
                    {
                        // 0 none 1 ODD 2 even
                        case 0:
                            this.serialPort.Parity = Parity.None;
                            break;
                        case 1:
                            this.serialPort.Parity = Parity.Odd;
                            break;
                        case 2:
                            this.serialPort.Parity = Parity.Even;
                            break;
                        default:
                            break;
                    }

                    switch (this.cmbStopBits.SelectedIndex)//停止位
                    {
                        // 0-1  1-1.5 2-2
                        case 0:
                            this.serialPort.StopBits = StopBits.One;
                            break;
                        case 1:
                            this.serialPort.StopBits = StopBits.OnePointFive;
                            break;
                        case 2:
                            this.serialPort.StopBits = StopBits.Two;
                            break;
                        default:
                            break;
                    }

                    this.serialPort.RtsEnable = this.ckbRTS.Checked;
                    this.serialPort.DtrEnable = this.ckbDTR.Checked;

                    this.serialPort.ReceivedBytesThreshold = 1;
                    this.serialPort.DataReceived += serialPort_DataReceived;

                    //打开串口
                    if (this.serialPort.IsOpen)
                    {
                        this.serialPort.Close();
                        this.serialPort.Dispose();//释放资源，如果不释放资源的话ui界面会卡死
                    }
                    this.serialPort.Open();
                    IsOpen = true;

                }
                catch (Exception ex)
                {

                    MessageBox.Show($"打开串口:{serialPort.PortName.ToString()}失败！" + ex);
                    this.tsslStatus.Text = "串口打开失败！";
                }
            }
        }

        #endregion


        #region 01_接收区相关
        //接收信息--（这里边有个bug，主线程和子线程同时进行的bug，子线程读取主线程数据是和主线程注入数据同时执行的）
        //***注：在接收线程中最好不要做什么很耗费时间的事情，最好要只做一些很单纯的事情,防止出现pv问题
        private void serialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
        {

            if (isReceivePause == true)//如果处于暂停状态直接结束                
                return;

            if (IsOpen == false)
                return;

            //Thread.Sleep(50);//等待主线程缓冲---error这个会出bug，
            //可能原因：主线程等待50ms就是为了让子线程好能读取，结果子线程也等了一样的时间，缓冲区数据还没有被取出来就被覆盖了

            //拿取缓冲区存储的数据
            byte[] dataTmp = new byte[serialPort.BytesToRead];
            serialPort.Read(dataTmp, 0, dataTmp.Length);

            receiveBuffer.AddRange(dataTmp);
            receiveCount += dataTmp.Length;//接收到的长度




            //1.不需要创建线程，因为这个监听时间的方法本身就在一个(辅助)线程中执行,
            //2.跨控件访问-->invork

            #region BeginInvoke 与 死锁风险

            /*
             * 死锁风险：更糟糕的是，如果你在 DataReceived 事件处理程序中使用了同步方法来更新UI（例如 Control.Invoke 而不是 BeginInvoke），
             * 并且这些调用又反过来尝试获取UI线程上的资源，那么就有可能形成死锁。
             * 这是因为 Close 方法正在等待 DataReceived 事件处理程序完成，同时UI线程被 Close 方法阻塞，无法处理来自 DataReceived 事件的UI更新请求。
             */

            #endregion
            this.BeginInvoke(new Action(() =>
            {
                //更新接收计数和已清空计数
                this.tsslReciveCount.Text = receiveCount.ToString();

                //如果没有勾选数据帧解析就按照原方式解析
                if (this.ckbStartDataFramesReceive.Checked == false)
                {
                    //编码转换
                    if (!ckbHexInRecive.Checked)
                    {
                        //选择编码格式
                        string str = Encoding.GetEncoding(myEncoding).GetString(dataTmp).Replace("\0", "\\0");

                        //接收
                        this.txtReciveMsg.AppendText(str);

                        #region 错误修改--此bug最好不要去解决

                        /***error1***

                        可能原因:进行两次反转编码会很浪费时间，会导致读的速度赶不上写入的速度，而缓冲区大小最多4096，会产生覆盖

                        //针对这种方式进行的无奈修改---
                        txtReciveMsg.Text = Transform.ToHexString(receiveBuffer.ToArray(), " ");

                        txtReciveMsg.Text = Encoding.GetEncoding(myEncoding).GetString(receiveBuffer.ToArray()).Replace("\0", "\\0");
                        */


                        /***error2***
                         
                        可能原因:线程的创建依然需要时间,也赶不上，总之，这里最好什么都不添加,或者让发送那边多休息一下，但是这也不现实

                        //尝试加入多线程
                        Task task = Task.Run(new Action(() => 
                        {        
                            //Thread.Sleep(50);

                            this.Invoke(new Action(() => 
                            {
                                txtReciveMsg.Text = Transform.ToHexString(receiveBuffer.ToArray(), " ");

                                txtReciveMsg.Text = Encoding.GetEncoding(myEncoding).GetString(receiveBuffer.ToArray()).Replace("\0", "\\0");
                            }));
                        }));
                        */
                        #endregion

                    }
                    else
                    {
                        //十六进制选中时
                        this.txtReciveMsg.AppendText(Transform.ToHexString(dataTmp, " "));
                    }
                }

                else
                {
                    //解析数据--重要数据结构：queue
                    foreach (byte item in dataTmp)
                    {
                        bufferQueue.Enqueue(item);
                    }

                    if (isHeaderReceive == false)//找到帧头（0x7f)
                    {

                        //foreach (byte item in bufferQueue)--error：***集合在枚举数实例化后进行了修改***

                        //原因：由于 bufferQueue 是一个队列，在便利他的时候出队入队会改变“集合”的结构!!!

                        //C#中，当你使用foreach或者其他的方法遍历List，字典等集合类型的时候，是不允许对集合进行增加或者删除的(此处的增加删除指的是
                        //修改了集合的结构，对于元素值的修改是允许的)

                        //丢弃帧头前不需要的数据
                        foreach (byte item in bufferQueue.ToArray())
                        {
                            if (item != 0x7f)
                            {
                                bufferQueue.Dequeue();
                            }
                            else
                            {
                                isHeaderReceive = true;
                                break;
                            }
                        }
                    }

                    //找到帧头后进行后续操作
                    if (isHeaderReceive == true)
                    {
                        //判断有数据帧的长度(长度不够以为着信息无效，至少应该有两位，帧头以及数据长度)
                        if (bufferQueue.Count >= 2)
                        {
                            ////控制台输出方便调试
                            //Console.WriteLine(DateTime.Now.ToLongTimeString());
                            //Console.WriteLine($"show the data in bufferQueue{Transform.ToHexString(bufferQueue.ToArray())}");
                            //Console.WriteLine($"frame length = {String.Format("{0:x2}", bufferQueue.ToArray()[1])}");


                            frameLength = bufferQueue.ToArray()[1];

                            //一帧完整的数据的长度判断，但是这个帧不一定是有效的
                            if (bufferQueue.Count >= 1 + 1 + frameLength + 2)//crc校验码占两位
                            {
                                //将其拷贝
                                byte[] frameBuffer = new byte[1 + 1 + frameLength + 2];
                                Array.Copy(bufferQueue.ToArray(), 0, frameBuffer, 0, frameBuffer.Length);


                                //CRC检验（只实现了crc检验）
                                if (CRC_Check(frameBuffer))
                                {

                                    //Console.WriteLine("crc_check is ok！get frames");
                                    //检验完毕显示在txt中
                                    this.txtDataFrames.Text = Transform.ToHexString(frameBuffer);
                                    this.txtData1.Text = String.Format("{0:x2}", frameBuffer[2]);
                                    this.txtData2.Text = String.Format("{0:x2}", frameBuffer[3]);
                                    this.txtData3.Text = String.Format("{0:x2}", frameBuffer[4]);
                                    this.txtData4.Text = String.Format("{0:x2}", frameBuffer[5]);

                                }
                                else
                                {
                                    Console.WriteLine("无效数据!");
                                    this.tsslStatus.Text = "crc检验失败，无效数据！";
                                }

                                //★无论有效无效，这个帧执行完要抛弃
                                for (int i = 0; i < frameBuffer.Length; i++)
                                {
                                    bufferQueue.Dequeue();
                                }

                                //头帧标志位初始化
                                isHeaderReceive = false;
                            }
                        }
                    }
                }
            }));

        }

        //CRC检验的方法:保证数据的完整性
        private bool CRC_Check(byte[] buffCheck)
        {
            //crc检验之前要先确定是当前的芯片(寄存器)是大端还是小端存储
            bool ret = false;

            byte[] tmp = new byte[buffCheck.Length - 2];//获取crc检验码之前的数据
            Array.Copy(buffCheck, 0, tmp, 0, tmp.Length);
            byte[] crcData = DataCheck.DataCrc16_Ccitt(tmp, DataCheck.BigOrLittle.BigEndian);

            //crc校验返回值和最初的缓存中的crc检验码进行对比
            //由于在数据翻转的时候是去除了crc校验的两位字节的，因此crc校验是 0-->buffCheck.Length - 2  1-->buffCheck.Length - 1
            if (crcData[0] == buffCheck[buffCheck.Length - 2] &&
                crcData[1] == buffCheck[buffCheck.Length - 1])
            {
                // crc_check ok!
                ret = true;
            }


            return ret;
        }

        #region 选择并接收文件
        //选择路径
        private void btnSelectFilePath_Click(object sender, EventArgs e)
        {
            //创建实例对象
            FolderBrowserDialog folderBrowserDialog = new FolderBrowserDialog();

            if (folderBrowserDialog.ShowDialog() == DialogResult.OK)
            {
                this.txtFilePathToRecive.Text = folderBrowserDialog.SelectedPath;
            }
        }

        //保存数据
        private void btnSaveData_Click(object sender, EventArgs e)
        {
            if (this.txtReciveMsg.Text.Length == 0)
            {
                MessageBox.Show("保存文件失败：读取不到文件！");
                this.tsslStatus.Text = "保存文件失败！读取不到文件！";
                return;
            }

            if (this.txtFilePathToRecive.Text.Length == 0)
            {
                MessageBox.Show("请先指定要保存到的文件夹！");
                this.tsslStatus.Text = "保存文件路径未选择！";
                return;
            }

            //定义保存的文件路径
            string fileName = this.txtFilePathToRecive.Text + "\\" + SavePath;

            //创建写入器
            using (StreamWriter sw = new StreamWriter(fileName))
            {
                //sw.Write(this.txtReciveMsg.Text);--error：乱码的地方写不进去

                sw.Write(Encoding.GetEncoding(myEncoding).GetString(this.receiveBuffer.ToArray()));//--->不多此一举


                sw.Flush();//清理写入器缓冲区
            }

            this.tsslStatus.Text = "文件保存成功！";
        }


        #endregion

        //暂停接收
        private void btnPause_Click(object sender, EventArgs e)
        {
            if (isReceivePause == false)
            {
                this.btnPause.Text = "取消暂停";
                isReceivePause = true;
            }
            else
            {
                this.btnPause.Text = "暂停";
                isReceivePause = false;
            }
        }

        //清空接收区
        private void btnClean_Click(object sender, EventArgs e)
        {
            this.txtReciveMsg.Text = "";
            receiveBuffer.Clear();
            this.tsslReciveCount.Text = "0";
            cleanCount += receiveCount;
            this.tsslCleanCount.Text = cleanCount.ToString();
            receiveCount = 0;

        }

        //自动清空
        private void ckbAutoClean_CheckedChanged(object sender, EventArgs e)
        {
            if (this.ckbAutoClean.Checked)
            {
                timerAutoClean.Start();
            }
            else
            {
                timerAutoClean.Stop();
            }
        }

        //timer事件--自动清空
        private void timerAutoClean_Tick(object sender, EventArgs e)
        {
            if (this.txtReciveMsg.Text.Length >= 4096)
            {
                this.txtReciveMsg.Text = "";
                this.receiveBuffer.Clear();
                this.tsslReciveCount.Text = "0";
                cleanCount += receiveCount;
                this.tsslCleanCount.Text = cleanCount.ToString();
                receiveCount = 0;
            }
        }

        //选中十六进制的状态发生变化--接受区
        private void ckbHexInRecive_CheckedChanged(object sender, EventArgs e)
        {
            //接收区清空时候不进行变化
            if (txtReciveMsg.Text == "")
                return;

            //选中的时候
            if (ckbHexInRecive.Checked)
            {
                txtReciveMsg.Text = Transform.ToHexString(receiveBuffer.ToArray(), " ");
            }
            else
            {
                txtReciveMsg.Text = Encoding.GetEncoding(myEncoding).GetString(receiveBuffer.ToArray()).Replace("\0", "\\0");
            }
        }

        #endregion


        #region 02_发送区相关

        #region 自动保存缓冲区
        
        //光标(或其他行为)离开发送区后的行为--这个只是将数据保存到缓冲区
        private void SaveToSendBuffer()
        {

            //反向推理十六进制勾选情况，显然若此时已经checked，那么要存储的是没有checked之前的数据
            //反之，如果是unchecked，那么在变化之前肯定是checked，也就是十六进制的
            if (ckbHexInSend.CheckState == CheckState.Unchecked)
            {
                //检测你输入的数据是否已经是十六进制
                if (DataEncoding.IsHexString(txtSendMsg.Text.Replace(" ", "")))
                {
                    //若是
                    sendBuffer.Clear();//先清空缓存
                    sendBuffer.AddRange(Transform.ToBytes(txtSendMsg.Text.Replace(" ", "")));
                }
                else
                {
                    //sendBuffer.Clear();
                    MessageBox.Show("请输入正确的十六进制数据！");
                    txtSendMsg.Select();
                }
            }
            else
            {
                sendBuffer.Clear();
                sendBuffer.AddRange(Encoding.GetEncoding(myEncoding).GetBytes(txtSendMsg.Text));
            }
        }

        #endregion

        /*十六进制转换--0x00如何进行转换的问题
        //private void txtSendMsg_TextChanged(object sender, EventArgs e)
        //{
        //    //十六进制转换--0x00如何进行转换的问题
        //}
        */

        //选中十六进制的状态发生变化--发送区
        private void ckbHexInSend_CheckedChanged(object sender, EventArgs e)
        {
            if (txtSendMsg.Text.Length == 0)
                return;
            
            SaveToSendBuffer();

            if (ckbHexInSend.Checked)
            {
                txtSendMsg.Text = Transform.ToHexString(sendBuffer.ToArray(), " ");
            }
            else
            {
                txtSendMsg.Text = Encoding.GetEncoding(myEncoding).GetString(sendBuffer.ToArray()).Replace("\0", "\\0");
            }
        }

        //清空发送
        private void btnCleanSend_Click(object sender, EventArgs e)
        {

            this.txtSendMsg.Text = "";

            this.tsslSendCount.Text = "0";

            sendCount = 0;
        }

        //发送信息的函数
        private void SendData()
        {

            #region 保存数据至缓冲区
            //检测是否勾选十六进制发送
            if (ckbHexInSend.CheckState == CheckState.Checked)
            {
                //检测你输入的数据是否已经是十六进制
                if (DataEncoding.IsHexString(txtSendMsg.Text.Replace(" ", "")))
                {
                    //若是
                    sendBuffer.Clear();//先清空缓存
                    sendBuffer.AddRange(Transform.ToBytes(txtSendMsg.Text.Replace(" ", "")));
                }
                else
                {
                    //sendBuffer.Clear();
                    MessageBox.Show("请输入正确的十六进制数据！");
                    txtSendMsg.Select();
                }
            }
            else
            {
                sendBuffer.Clear();
                sendBuffer.AddRange(Encoding.GetEncoding(myEncoding).GetBytes(txtSendMsg.Text));
            }
            #endregion

            byte[] byteSendFile = sendBuffer.ToArray();

            //限制单次发送字节数（因为serialPort的特性就是每次最多发送4096个字节）
            int fileSendCounts = byteSendFile.Length / 4096;//需要发多少次
            int fileSendRemain = byteSendFile.Length % 4096;//发送后不足一次的

            //循环发送
            for (int i = 0; i < fileSendCounts; i++)
            {

                serialPort.Write(byteSendFile, 4096 * i, 4096);

                //动态休眠调整
                if (serialPort.BytesToWrite > 4096)
                {
                    Thread.Sleep(100);
                }
                else
                {
                    Thread.Sleep(50);//留足处理时间，避免缓冲区溢出，提高稳定性
                }
            }

            if (fileSendRemain > 0)
            {
                //发送剩余的
                serialPort.Write(byteSendFile, 4096 * fileSendCounts, fileSendRemain);
            }

            //更新发送计数
            sendCount += byteSendFile.Length;
        }

        //发送信息
        private void btnSend_Click(object sender, EventArgs e)
        {

            if (isOpen == false)
            {
                MessageBox.Show("尚未打开端口！", "提示信息");
                return;
            }

            if (this.txtSendMsg.Text.Trim().Length == 0)
            {
                MessageBox.Show("请先在发送区输入数据！", "提示信息");
                return;
            }

            try
            {
                //serialPort1.Write(this.txtSendMsg.Text);//发送数据
                SendData();
                tsslSendCount.Text = sendCount.ToString();
            }
            catch (Exception ex)
            {

                MessageBox.Show("发送数据失败！" + ex);
            }

        }

        #region 自动发送相关
        //自动发送
        private void ckbAutoSend_CheckedChanged(object sender, EventArgs e)
        {

            if (IsOpen == false && this.ckbAutoSend.CheckState == CheckState.Checked)//未开启串口无法自动发送
            {
                this.ckbAutoSend.CheckState = CheckState.Unchecked;

                //停止定时器
                this.timerAutoSend.Enabled = false;

                this.tsslStatus.Text = "自动发送失败，串口未打开！";
                return;
            }

            else if (IsOpen == true && this.ckbAutoSend.CheckState == CheckState.Checked)
            {
                //手动发送禁止
                this.btnSend.Enabled = false;

                //设置发送周期
                int interval = 0;

                if (int.TryParse(this.txtAutoCycle.Text.Trim(), out interval))
                {
                    if (interval < -1 || interval > 60000)
                    {
                        interval = 1000;
                        this.txtAutoCycle.Text = "1000";
                        this.tsslStatus.Text = "周期设定不在范围内,限制为1000ms!";
                    }


                    this.timerAutoSend.Interval = interval;

                    //绑定激发事件
                    this.timerAutoSend.Elapsed += TimerAutoSend_Elapsed;

                    //开启timer
                    this.timerAutoSend.Enabled = true;
                }

                else
                {
                    this.ckbAutoSend.CheckState = CheckState.Unchecked;
                    this.tsslStatus.Text = "自动发送周期格式设定错误!";
                    this.txtAutoCycle.Select();

                    //停止定时器
                    this.timerAutoSend.Enabled = false;
                    return;
                }
            }

            else//所有没有勾选的情况
            {
                //停止计时器
                if (this.timerAutoSend.Enabled == true)
                    this.timerAutoSend.Enabled = false;

                this.btnSend.Enabled = true;
                this.tsslStatus.Text = "自动发送已停止!";
            }

        }

        //达到间隔时间发生的事件
        private void TimerAutoSend_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
        {
            this.Invoke(new Action(() =>
            {
                SendData();
                tsslSendCount.Text = sendCount.ToString();
            }));
        }
        #endregion

        #region 选择并发送文件
        //打开指定文件，并将其放置于发送区
        private void btnOpenFile_Click(object sender, EventArgs e)
        {
            //创建 OpenFileDialog 实例对象
            OpenFileDialog openFileDialog = new OpenFileDialog();


            //设置其属性
            openFileDialog.Title = "请选择要发送的文件";
            openFileDialog.Filter = "文本文件(*.txt)|*.txt|二进制文件(*.bin)|*.bin|十六进制文件(*.hex)|*.hex"; //文件类型过滤器
            openFileDialog.RestoreDirectory = true;//还原初始化目录

            //打开OpenFileDialog
            if (openFileDialog.ShowDialog() == DialogResult.OK)
            {
                //取消十六进制显示(部分文件没法使用十六进制进行发送)
                this.ckbHexInSend.Checked = false;

                //获取文件名称
                string fileName = openFileDialog.FileName;
                this.txtFilePathToSend.Text = fileName;

                try
                {
                    //以指定格式读取文件
                    using (StreamReader sr = new StreamReader(fileName, Encoding.GetEncoding(myEncoding)))
                    {
                        this.txtSendMsg.Text = sr.ReadToEnd();//读取到发送区


                        //***非常关键的一步***
                        //光标必须移动到发送区，否则到时候移动光标进入缓存的函数他就没法执行了
                        this.txtSendMsg.Select();


                        this.tsslStatus.Text = "文件读取成功！";

                    }
                }
                catch (Exception ex)
                {

                    MessageBox.Show("文件读取失败!" + ex, "错误提示");
                    this.tsslStatus.Text = "文件读取失败！";
                }
            }
            else
            {
                this.tsslStatus.Text = "打开文件失败！";
                return;
            }
        }


        //发送文件
        private void btnSendFile_Click(object sender, EventArgs e)
        {
            if (IsOpen == false)
            {
                this.tsslStatus.Text = "发送文件失败：串口未连接！";
                MessageBox.Show("需打开串口才能发送指定文件！", "提示信息");

                return;
            }
            else
            {
                if (this.txtSendMsg.Text.Length == 0)
                {
                    this.tsslStatus.Text = "发送文件失败：文件为空！";
                    MessageBox.Show("不能发送空文件！", "提示信息");

                    return;
                }


                //SendData();-->一次发送过多数据不允许

                //创建新线程
                Task task = Task.Run(() =>
                {
                    try
                    {
                        #region 发送部分已被提取至senddata函数
                        ////读取至字节流
                        //byte[] byteSendFile = Encoding.UTF8.GetBytes(this.txtSendMsg.Text);


                        //不需要了，所有要发送的内容在离开接收区的那一刻已经被存在sendBuffer里了                   
                        //byte[] byteSendFile = sendBuffer.ToArray();

                        ////限制单次发送字节数
                        //int fileSendCounts = byteSendFile.Length / 4096;//需要发多少次
                        //int fileSendRemain = byteSendFile.Length % 4096;//发送后不足一次的


                        ////循环发送
                        //for (int i = 0; i < fileSendCounts; i++)
                        //{
                        //    serialPort.Write(byteSendFile, 4096 * i, 4096);

                        //    Thread.Sleep(50);//留足处理时间，避免缓冲区溢出，提高稳定性
                        //}

                        //if (fileSendRemain > 0)
                        //{
                        //    //发送剩余的
                        //    serialPort.Write(byteSendFile, 4096 * fileSendCounts, fileSendRemain);
                        //}

                        ////更新发送计数
                        //sendCount += byteSendFile.Length;

                        #endregion

                        this.Invoke(new Action(() =>
                        {
                            SendData();
                            this.tsslSendCount.Text = sendCount.ToString();
                            this.tsslStatus.Text = "发送文件成功！";
                        }));
                    }
                    catch (Exception ex)
                    {

                        this.Invoke(new Action(() =>
                        {
                            this.tsslStatus.Text = "发送文件失败！";
                            MessageBox.Show("发送文件失败！" + ex);
                        }));

                    }
                });


            }
        }
        #endregion

        #endregion


        #region 03_快捷指令相关
        //右键点击事件
        private void btnQuickCmd_MouseDown(object sender, MouseEventArgs e)
        {
            //判断是不是右键点击
            if (e.Button == MouseButtons.Right)
            {
                if (sender is Button btn)
                {
                    //Button btn = sender as Button;--等价写法，语法糖

                    FrmQuickCommand objFrmQucikCmd = new FrmQuickCommand(btn.Text, btn.Tag == null ? string.Empty : btn.Tag.ToString());

                    if (objFrmQucikCmd.ShowDialog() == DialogResult.OK)
                    {
                        //获取最新设置的值
                        btn.Text = objFrmQucikCmd.CommandName;
                        btn.Tag = objFrmQucikCmd.CommandContent;
                        btn.Select();

                        //保存(通过settings的方式来保存，也可以使用配置文件的方式，其实都行)
                        switch (btn.Name)
                        {
                            case "btnQuickCmd01":
                                Settings.Default.QuickButton01 = objFrmQucikCmd.CommandName;
                                Settings.Default.QuickCommand01 = objFrmQucikCmd.CommandContent;
                                this.txtQuickCmd01.Text = btn.Tag.ToString();
                                break;

                            case "btnQuickCmd02":
                                Settings.Default.QuickButton02 = objFrmQucikCmd.CommandName;
                                Settings.Default.QuickCommand02 = objFrmQucikCmd.CommandContent;
                                this.txtQuickCmd02.Text = btn.Tag.ToString();
                                break;

                            case "btnQuickCmd03":
                                Settings.Default.QuickButton03 = objFrmQucikCmd.CommandName;
                                Settings.Default.QuickCommand03 = objFrmQucikCmd.CommandContent;
                                this.txtQuickCmd03.Text = btn.Tag.ToString();
                                break;

                            case "btnQuickCmd04":
                                Settings.Default.QuickButton04 = objFrmQucikCmd.CommandName;
                                Settings.Default.QuickCommand04 = objFrmQucikCmd.CommandContent;
                                this.txtQuickCmd04.Text = btn.Tag.ToString();
                                break;

                            default:
                                break;
                        }

                        //保存
                        Settings.Default.Save();

                    };
                }
            }

        }

        //快捷指令点击事件
        private void btnQuickCmd_Click(object sender, EventArgs e)
        {
            if (IsOpen == false)
            {
                MessageBox.Show("串口未打开!", "提示信息");
                return;
            }
            else
            {
                if (sender is Button btn)
                {
                    if (btn.Tag != null && btn.Tag.ToString().Length > 0)//第一个判断是否存在tag属性，第二个判断即使存在是不是为空值
                    {
                        string strSend = btn.Tag.ToString();

                        SendCommand(strSend);
                    }

                }
            }
        }
        #endregion


        #region 04_命令模式相关
        //发送指令的函数（命令模式和快捷指令共用）
        private void SendCommand(string strCmd)
        {
            this.txtSendMsg.Text = strCmd;

            byte[] tmp = DataEncoding.HexStringToBytes(strCmd.Replace(" ", ""));

            try
            {
                //发送
                this.serialPort.Write(tmp, 0, tmp.Length);

                //更新发送计数
                sendCount += tmp.Length;
                tsslSendCount.Text = sendCount.ToString();
            }
            catch (Exception ex)
            {

                MessageBox.Show("命令发送失败！" + ex, "提示信息");
                this.tsslStatus.Text = "命令发送失败！";
            }
        }

        //发送命令
        private void btnSendCmd_Click(object sender, EventArgs e)
        {
            if (IsOpen == false)
            {
                MessageBox.Show("串口未打开！", "提示信息");
                this.tsslStatus.Text = "发送失败，串口未打开！";
                return;
            }

            bool DataVerify = true;//检验如果输入数据是否是合法的十六进制数

            //使用 &= 只要有一个不合格，所有的都不合格
            if (this.ckbFrameHeader.Checked)
            {
                DataVerify &= DataEncoding.IsHexString(this.txtFrameHeader.Text.Trim());
            }
            if (this.ckbData.Checked)
            {
                DataVerify &= DataEncoding.IsHexString(this.txtData.Text.Trim());
            }
            if (this.ckbFrameTail.Checked)
            {
                DataVerify &= DataEncoding.IsHexString(this.txtFrameTail.Text.Trim());
            }

            if (DataVerify == false)
            {
                MessageBox.Show("请输入正确的十六进制格式数据", "提示信息");
                this.tsslStatus.Text = "命令格式错误！";
                return;
            }

            //字符串拼接
            string fullCommand = string.Empty;
            if (this.ckbFrameHeader.Checked)
            {
                fullCommand += this.txtFrameHeader.Text.Trim();
            }
            if (this.ckbData.Checked)
            {
                fullCommand += this.txtData.Text.Trim();
            }

            //帧尾不能写在这里，不能带着帧尾进入校验
            /*
            if (this.ckbFrameTail.Checked)
            {
                fullCommand += this.txtFrameTail.Text.Trim();
            }
            */

            //加入校验模式
            if (this.ckbCheck.Checked)
            {
                //字节流化
                byte[] byteCmd = DataEncoding.HexStringToBytes(fullCommand.Replace(" ", ""));


                //可以学习这种写法
                myCheck myCheck = (myCheck)Enum.Parse(typeof(myCheck), this.cmbCheck.Text, true);//加true不区分大小写

                //附加校验，将校验值附加到数据的结尾
                switch (myCheck)
                {
                    case SerialPortExp.myCheck.XOR异或校验:
                        fullCommand = DataEncoding.BytesToHexString(DataCheck.DataXORFull(byteCmd));
                        break;
                    case SerialPortExp.myCheck.SUM8累加校验:
                        fullCommand = DataEncoding.BytesToHexString(DataCheck.DataSum8Full(byteCmd));
                        break;
                    case SerialPortExp.myCheck.CRC16小端:
                        fullCommand = DataEncoding.BytesToHexString(DataCheck.DataCrc16Full_Ccitt(byteCmd, DataCheck.BigOrLittle.LittleEndian));
                        break;
                    case SerialPortExp.myCheck.CRC16大端:
                        fullCommand = DataEncoding.BytesToHexString(DataCheck.DataCrc16Full_Ccitt(byteCmd, DataCheck.BigOrLittle.BigEndian));
                        break;
                    case SerialPortExp.myCheck.SUM16小端:
                        fullCommand = DataEncoding.BytesToHexString(DataCheck.DataSum16Full(byteCmd, DataCheck.BigOrLittle.LittleEndian));
                        break;
                    case SerialPortExp.myCheck.SUM16大端:
                        fullCommand = DataEncoding.BytesToHexString(DataCheck.DataSum16Full(byteCmd, DataCheck.BigOrLittle.BigEndian));
                        break;
                    case SerialPortExp.myCheck.ModBusCRC小端:
                        fullCommand = DataEncoding.BytesToHexString(DataCheck.DataCrc16Full_Modbus(byteCmd, DataCheck.BigOrLittle.LittleEndian));
                        break;
                    case SerialPortExp.myCheck.ModBusCRC大端:
                        fullCommand = DataEncoding.BytesToHexString(DataCheck.DataCrc16Full_Modbus(byteCmd, DataCheck.BigOrLittle.BigEndian));
                        break;
                    default:
                        break;
                }
            }

            //加上帧尾
            if (this.ckbFrameTail.Checked)
            {
                fullCommand += this.txtFrameTail.Text.Trim();
            }

            //发送
            //SendData();
            SendCommand(fullCommand);
        }

        #endregion


        #region 05_其他额外设置

        //清空计数
        private void toolStripStatusLabel7_Click(object sender, EventArgs e)
        {
            this.cleanCount += sendCount + receiveCount;
            this.tsslCleanCount.Text = cleanCount.ToString();

            this.sendCount = 0;
            this.tsslSendCount.Text = "0";

            this.receiveCount = 0;
            this.tsslReciveCount.Text = "0";
        }

        //选择编码格式
        private void tsslEncodingSelect_Click(object sender, EventArgs e)
        {
            if (sender is ToolStripMenuItem tsmi)
            {
                //刷新
                tsslgb2312.Text = "gb2312";
                tsslUTF8.Text = "UTF-8";

                tsmi.Text += "    √";
                this.myEncoding = tsmi.Tag.ToString();

                this.tsslStatus.Text = $"编码格式修改为：{tsmi.Tag}!";
            }
        }

        //关闭之前保存命令模式中的配置信息
        private void MainFrm_FormClosing(object sender, FormClosingEventArgs e)
        {
            //保存 txt文本 配置信息
            Settings.Default.CmdHead = this.txtFrameHeader.Text;
            Settings.Default.CmdData = this.txtData.Text;
            Settings.Default.CmdCheck = this.cmbCheck.SelectedIndex;
            Settings.Default.CmdTail = this.txtFrameTail.Text;

            //保存 ckb 勾选信息
            Settings.Default.CkbHead = this.ckbFrameHeader.Checked;
            Settings.Default.CkbData = this.ckbData.Checked;
            Settings.Default.CkbCheck = this.ckbCheck.Checked;
            Settings.Default.CkbTail = this.ckbFrameTail.Checked;

            Settings.Default.Save();
        }


        #endregion

    }

}
